/**
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.

 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package org.jimm.protocols.icq;

import org.jimm.protocols.icq.exceptions.RawDataBadForcedLengthException;

/**
 * <p>Created by
 *   @author Fabrice Michellonet
 */
public class RawData extends DataField {

	/** Byte length representation  */
	public static final int BYTE_LENGTH = 1;

	/** Word length representation  */
	public static final int WORD_LENGTH = 2;

	/** Double Word length representation  */
	public static final int DWORD_LENGTH = 4;

	private int value;
	private int forcedLenght = 0;
	private String stringValue = null;
	private boolean reversed = false;

	/**
	 * Construct a RawData containing a numeric value, but affect that value into the specified
	 * type of variable [byte, Word, DWord].
	 *
	 * @param value The RawData's numeric value.
	 * @param forcedLenght The type of variable.
	 */
	public RawData(int value, int forcedLenght) {
		this.value = value;
		this.forcedLenght = forcedLenght;

		long maxValue = 0;
		for (int i = 0; i < forcedLenght; i++) {
			maxValue = ( (maxValue << 8) | 0xFF);
		}
		if (value <= maxValue)
			insert(value, forcedLenght);
		else {
			throw new RawDataBadForcedLengthException(value + " cannot fit in " +
					forcedLenght + " Byte(s).");
		}
	}

	/**
	 * Construct a RawData containing a numeric value.
	 *
	 * @param value The RawData's numeric value.
	 */
	public RawData(int value) {
		this.value = value;

		long maxValue = 0xFF;
		int i = 1;
		while (maxValue < value) {
			maxValue = ( (maxValue << 8) | 0xFF);
			i++;
		}
		this.insert(value, i);
	}

	/**
	 * Construct a RawData containing a String.
	 *
	 * @param string The RawData's String value.
	 */
	public RawData(String string) {
		stringValue = string;
		insertString(string);
	}

	public RawData(byte[] array) {
		this.byteArray = array;
		this.value = getByte(array);
		this.stringValue = new String(byteArray);
	}

	public RawData(byte[] value, int start, int len) {
		byte[] tmp = new byte[len];
		System.arraycopy(value, start, tmp, 0, len);
		
		byteArray = tmp;
		this.value = getByte(tmp);
		this.stringValue = new String(tmp);
	}

	private static int getByte(byte[] array) {
		int result = 0;
		for (int i = 0; i < array.length; i++) {
			long shift = ( (array.length - (i + 1)) * 8);
			long mask = 0xFF << shift;
			result |= (array[i] << shift) & mask;
		}
		return result;
	}

	private void insert(int value, int len) {
		byteArray = new byte[len];
		for (int i = 0; i < len; i++) {
			byteArray[i] = (byte) ( (value >> ( ( (len - 1) - i) * 8)) & 0xFF);
		}
	}

	private void insertString(String string) {		
		byteArray = new byte[string.length()];
		for (int i = 0; i < string.length(); i++) {
			byteArray[i] = (byte) string.charAt(i);		
		}
	}

	public void invertIndianness() {
		byte[] tmp = new byte[byteArray.length];
		for (int i = 0; i < byteArray.length; i++) {
			tmp[i] = byteArray[byteArray.length - 1 - i];
		}
		this.byteArray = tmp;
		reversed = true;
	}

	/**
	 *
	 * @return The arithmetic value of the RawData.
	 */
	public int getValue() {
		if (reversed) {
			value = 0;
			for (int i = 0; i < byteArray.length; i++) {
				value |= (byteArray[i] << (byteArray.length - 1 - i) * 8) &
				0xFFL << (byteArray.length - 1 - i) * 8;
			}
			reversed = false;
		}
		
		return value;
	}

	/**
	 *
	 * @return The data's length corresponding value;
	 */
	public int getForcedLenght() {
		return forcedLenght;
	}

	/**
	 *
	 * @return The RawData's String value.
	 */
	public String getStringValue() {
		if ( (stringValue == null) || (reversed == true)) {
			stringValue = new String(byteArray);
		}
		reversed = false;
		
		return stringValue;
	}
	
	/**
	 * 
	 * @return The integer RawData as String value
	 */
	public String toStringValue() {
		return String.valueOf(getValue());
	}

	/**
	 *
	 * @return The RawData's byte array representation.
	 */
	public byte[] getByteArray() {
		return byteArray;
	}
}
